package xaj.weavingtest.ast.istype;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import abc.aspectj.ast.*;

import abc.aspectj.parse.sym;
import abc.weaving.aspectinfo.AbcFactory;
import abc.weaving.aspectinfo.MethodCategory;
import polyglot.ast.Expr;
import polyglot.ast.Formal;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ext.jl.parse.Name;
import polyglot.lex.Identifier;
import polyglot.types.TypeSystem;
import polyglot.util.Position;
import xaj.xc.ast.NodeXAJ;

import polyglot.ast.*;
import polyglot.types.*;
import polyglot.util.*;
import polyglot.visit.*;
import java.util.*;

import abc.aspectj.ast.MakesAspectMethods;
import abc.aspectj.ast.AJNodeFactory;
import abc.aspectj.types.AJTypeSystem;
import abc.aspectj.types.AJContext;
import abc.aspectj.visit.AspectMethods;
import abc.aspectj.visit.AspectReflectionInspect;
import abc.aspectj.visit.AspectReflectionRewrite;

import abc.main.Debug;



//Versão que deve ser compilada/gerado automaticamente
public class IsType extends PCIf_c
implements MakesAspectMethods, NodeXAJ, xaj.lang.Map {
	
	final static int TEST = 0;
	final static int TRUE = 1;
	final static int FALSE = 2;
	
	int assign = 0;

    protected Expr expr;
    protected String methodName;
    protected MethodDecl methodDecl, mdTRUE, mdFALSE;

	
	private Map/*<String, Object>*/ map;
	private static NodeFactory nf = new xaj.weavingtest.ast.XAJNodeFactory_c();
	
	private Name typeName;
	
	private static Expr buildExpr(Position pos, Expr expr, Name typeName) {
		Expr expr1 = nf.ClassLit(pos, typeName.toType());
		Identifier id1 = new Identifier(pos, "isInstance", sym.IDENTIFIER);
		Expr expr2 = nf.Call(pos, expr1, id1.getIdentifier(), expr);
		return expr2;
	}
	
	public IsType(Position pos, Expr expr, Name typeName) {
		super(pos, buildExpr(pos, expr, typeName));
		this.expr = buildExpr(pos, expr, typeName);
		
		this.typeName = typeName;
		this.map = new HashMap();
		System.out.println("Criou um objeto IsType");
	}
	
	public Object get(String name) {
		return map.get(name);
	}

	public boolean add(String name, Object value) {
		if(!map.containsKey(name)) {
			map.put(name, value);
			return true;
		} else
			return false;
	}

	public Iterator iterator() {
		return map.keySet().iterator();
	}

	public void set(String name, Object value) {
		if(map.containsKey(name)) {
			map.keySet();
			map.put(name, value);
		}
	}
	
	public Node desugar(NodeFactory ft, TypeSystem ts) {		
		return this;
	}

    public void prettyPrint(CodeWriter w, PrettyPrinter tr) {
		w.write("ifIsType(");
        print(expr, w, tr);
        w.write(")");
    }
    
	/** Reconstruct the pointcut. */
	protected IsType reconstruct(Expr expr) {
	   if (expr != this.expr) {
		   IsType n = (IsType) copy();
		   n.expr = expr;
		   return n;
	   }

	   return this;
	}

	/** Visit the children of the pointcut. */
	public Node visitChildren(NodeVisitor v) {
	   Expr expr = (Expr) visitChild(this.expr, v);
	   return reconstruct(expr);
	}

	/** Type check the pointcut. */
	public Node typeCheck(TypeChecker tc) throws SemanticException {
		TypeSystem ts = tc.typeSystem();
        
		AJContext c = (AJContext) tc.context();
		if (c.inDeclare() && !Debug.v().allowDynamicTests)
			throw new SemanticException("if(..) requires a dynamic test and cannot be used inside a \"declare\" statement", position());
		
		if (! ts.equals(expr.type(), ts.Boolean())) {
			throw new SemanticException(
			"Condition of if pointcut must have boolean type.",
			expr.position());
		}
		
		return this;
	}

    protected boolean hasJoinPoint=false;
    protected boolean hasJoinPointStaticPart=false;
    protected boolean hasEnclosingJoinPointStaticPart=false;

    protected LocalInstance thisJoinPointInstance=null;
    protected LocalInstance thisJoinPointStaticPartInstance=null;
    protected LocalInstance thisEnclosingJoinPointStaticPartInstance=null;
    
	private LocalInstance thisJoinPointInstance(AJTypeSystem ts) {
	   if (thisJoinPointInstance==null)
		   thisJoinPointInstance = ts.localInstance(position(),Flags.FINAL,ts.JoinPoint(),"thisJoinPoint");
	   return thisJoinPointInstance;
   }

   private LocalInstance thisJoinPointStaticPartInstance(AJTypeSystem ts) {
		if (thisJoinPointStaticPartInstance==null)
			thisJoinPointStaticPartInstance = ts.localInstance(position(),Flags.FINAL,ts.JoinPointStaticPart(),"thisJoinPointStaticPart");
		return thisJoinPointStaticPartInstance;
	}
 
   private LocalInstance thisEnclosingJoinPointStaticPartInstance(AJTypeSystem ts) {
	   if (thisEnclosingJoinPointStaticPartInstance==null)
		   thisEnclosingJoinPointStaticPartInstance = ts.localInstance(position(),Flags.FINAL,
																	   ts.JoinPointStaticPart(),"thisEnclosingJoinPointStaticPart");
	   return thisEnclosingJoinPointStaticPartInstance;
   }
	

    protected boolean canRewriteThisJoinPoint=false;

    public MethodDecl exprMethod(AJNodeFactory nf, AJTypeSystem ts, List formals, ParsedClassType container){
    	
    	Return ret=null;
    	
    	if(assign == TEST)
			ret = nf.Return(position(),expr);
    	else if(assign == TRUE)
    		ret = nf.Return(position(), nf.BooleanLit(position(), true));
    	else if(assign == FALSE)
    		ret = nf.Return(position(), nf.BooleanLit(position(), false));
		
    	Block bl = nf.Block(position()).append(ret);
		TypeNode retType = nf.CanonicalTypeNode(position(),ts.Boolean());
		List args = new LinkedList(formals);
		List throwTypes = new LinkedList();
		for (Iterator i = expr.throwTypes(ts).iterator(); i.hasNext(); ) {
			Type t = (Type) i.next();
			TypeNode tn = nf.CanonicalTypeNode(position(),t);
			throwTypes.add(tn);
		}
		List formaltypes = new ArrayList();
		Iterator fi = formals.iterator();
		while (fi.hasNext()) {
		    Formal f = (Formal)fi.next();
		    formaltypes.add(f.type().type());
		}

                addJoinPointFormals(nf, ts, args, formaltypes);

		methodName = UniqueID.newID("if");
		MethodDecl md = nf.MethodDecl(position(),Flags.STATIC.Public(),retType,methodName,args,throwTypes,bl);
		MethodInstance mi = ts.methodInstance(position, container,
						      Flags.STATIC.Public(), retType.type(), methodName,
						      new ArrayList(formaltypes),
						      new ArrayList(expr.del().throwTypes(ts)));
		container.addMethod(mi);
		md = md.methodInstance(mi);
//		methodDecl = md;
		return md;
	}

    protected void addJoinPointFormals(AJNodeFactory nf, AJTypeSystem ts,
                                           List args, List formaltypes)
        {
		if (hasJoinPointStaticPart()) {
		    addJoinPointFormal(nf, ts, args, formaltypes,
		                       ts.JoinPointStaticPart(),
                                       "thisJoinPointStaticPart",
                                       thisJoinPointStaticPartInstance(ts));
                }
		if (hasJoinPoint()) {
		    addJoinPointFormal(nf, ts, args, formaltypes,
		                       ts.JoinPoint(),
                                       "thisJoinPoint",
                                       thisJoinPointInstance(ts));
                }
		if (hasEnclosingJoinPointStaticPart()) {
		    addJoinPointFormal(nf, ts, args, formaltypes,
		                       ts.JoinPointStaticPart(),
                                       "thisEnclosingJoinPointStaticPart",
                                       thisEnclosingJoinPointStaticPartInstance(ts));
                }
        }

        protected void addJoinPointFormal(AJNodeFactory nf, AJTypeSystem ts,
                                          List args, List formaltypes,
                                          ClassType jpfType, String name,
                                          LocalInstance li)
        {
            TypeNode tn = nf.CanonicalTypeNode(position(), jpfType);
            Formal jpf = nf.Formal(position(), Flags.FINAL, tn, name);
            jpf = jpf.localInstance(li);
            args.add(jpf);
            formaltypes.add(jpfType);
        }

	public PCIf liftMethod(AJNodeFactory nf){
		Expr exp = nf.Call(position(),methodName);
		return reconstruct(exp);
	}

    public abc.weaving.aspectinfo.Pointcut makeAIPointcut() {
	int lastpos = methodDecl.formals().size();
	int jp = -1, jpsp = -1, ejp = -1;
	if (hasEnclosingJoinPointStaticPart) ejp = --lastpos;
	if (hasJoinPoint) jp = --lastpos;
	if (hasJoinPointStaticPart) jpsp = --lastpos;

	MethodCategory.register(methodDecl, MethodCategory.IF_EXPR);

	MethodCategory.register(mdFALSE, MethodCategory.IF_EXPR);
	MethodCategory.register(mdTRUE, MethodCategory.IF_EXPR);

	List vars = new ArrayList();
	
	Iterator fi = methodDecl.formals().iterator();
	while (fi.hasNext()) {
	    Formal f = (Formal)fi.next();
	    vars.add(new abc.weaving.aspectinfo.Var(f.name(), f.position()));
	}
	return new If_isType
	    (typeName.name, vars,
	    		AbcFactory.MethodSig(methodDecl),
	    		AbcFactory.MethodSig(mdFALSE),
	    		AbcFactory.MethodSig(mdTRUE),
	    		jp,jpsp,ejp,position);
    }


    public Context enterScope(Context c) {
    	AJContext ajc = ((AJContext) c.pushStatic()).pushIf();
		AJTypeSystem ts = (AJTypeSystem)ajc.typeSystem();
		LocalInstance jp = thisJoinPointInstance(ts);
		ajc.addVariable(jp);
		LocalInstance sjp = thisJoinPointStaticPartInstance(ts);
		ajc.addVariable(sjp);
		LocalInstance ejpsp = thisEnclosingJoinPointStaticPartInstance(ts);
		ajc.addVariable(ejpsp);
		return ajc;
    }  

    public void aspectMethodsEnter(AspectMethods visitor)
    {
	visitor.pushPCIf(this);
    }

    public Node aspectMethodsLeave(AspectMethods visitor, AJNodeFactory nf,
            AJTypeSystem ts)
	{
    	List formals = calculateMethodParameters(visitor, nf, ts);
		
		assign = FALSE;
    	mdFALSE = exprMethod(nf, ts, formals, visitor.container());
		visitor.addMethod(mdFALSE);
		
		assign = TRUE;
		mdTRUE = exprMethod(nf, ts, formals, visitor.container());
		visitor.addMethod(mdTRUE);
		
		assign = TEST;
		methodDecl = exprMethod(nf, ts, formals, visitor.container());
		visitor.addMethod(methodDecl);
		
//		methodDecl = mdTRUE;
		
		visitor.popPCIf();
		
		return liftMethod(nf); // replace expression by method call
	}
    
    protected List calculateMethodParameters(AspectMethods visitor,
                                            AJNodeFactory nf, AJTypeSystem ts)
    {
        // construct method for expression in if(..).
        // When the if(..) occurs inside a cflow, the parameters are only the
        // variables bound inside that cflow. Otherwise, the parameters are exactly
        // the formals of the enclosing named pointcut or advice.
        AJContext ajc = (AJContext) visitor.context();
        List formals = new ArrayList();
        if (ajc.inCflow()) {
            Collection cflowVars = ajc.getCflowMustBind();
            for (Iterator varit = cflowVars.iterator(); varit.hasNext(); ) {
                String varName = (String) varit.next();
                LocalInstance li = (LocalInstance) ajc.findVariableSilent(varName);
                TypeNode tn = nf.CanonicalTypeNode(li.position(),li.type());
                Formal vf = nf.Formal(li.position(),Flags.FINAL,tn,varName).localInstance(li);
                formals.add(vf);
            }
        } else formals = visitor.formals();

        return formals;
    }

    public void enterAspectReflectionInspect(AspectReflectionInspect v,Node parent) {
	v.enterAdvice();
    }

    public void leaveAspectReflectionInspect(AspectReflectionInspect v) {
	canRewriteThisJoinPoint=v.leaveAdvice();
    }

    public void enterAspectReflectionRewrite(AspectReflectionRewrite v,AJTypeSystem ts) {
	v.enterAdvice(canRewriteThisJoinPoint ? thisJoinPointStaticPartInstance(ts) : null);
    }

    public Node leaveAspectReflectionRewrite(AspectReflectionRewrite v,AJNodeFactory nf) {
	v.leaveAdvice();
	return this;
    }
}
